Skip to content

Conversation

@jaguilar
Copy link
Contributor

@jaguilar jaguilar commented Dec 5, 2025

Any Pybricks device running btstack can now issue Bluetooth classic inquiry scans to find and report discoverable devices.

This API is experimental and we can change it later if we don't like it. But it's nice because it can be used to concretely demonstrate that the EV3's BTStack layer is doing something! This also should give some indication of the structure by which we'll add further classic functionality (more functions and switch entries in bluetooth_btstack_classic.c). If it's preferred to have it in the same file as the LE code, we can, but this structure is scarcely more expensive and a little more neatly organized.

You can test it with this program:

from pybricks.tools import run_task
from pybricks.experimental.btc import scan


async def do_scan():
    print("Start bluetooth scan...")
    devices = await scan()
    print("Scan complete, devices found: ")
    for device in devices:
        print(device)


run_task(do_scan())

@coveralls
Copy link

coveralls commented Dec 5, 2025

Coverage Status

coverage: 50.566% (+0.02%) from 50.55%
when pulling ebf3c1e on jaguilar:ev3-bluetooth-scan
into fcfdf4e on pybricks:master.

@jaguilar jaguilar force-pushed the ev3-bluetooth-scan branch from de407e3 to cb7bfec Compare January 4, 2026 03:03
@jaguilar
Copy link
Contributor Author

jaguilar commented Jan 4, 2026

Rebased this commit atop all the other refactoring work that has happened recently to btstack, and verified that everything is still working.

The ev3 can now scan for discoverable Bluetooth
devices.
@jaguilar jaguilar force-pushed the ev3-bluetooth-scan branch from cb7bfec to ebf3c1e Compare January 4, 2026 03:35
@laurensvalk
Copy link
Member

laurensvalk commented Jan 9, 2026

Thanks for submitting this! I can confirm that this is giving scan results.

After working on Bluetooth/BLE for the past couple of weeks, I have a few ideas on how we might integrate this into the Bluetooth driver.

I'll post an alternate draft PR soon. Hope that's OK. My goal isn't to rewrite all PRs. Once we've established a good pattern I think we can extend it quite easily to other functionality.


I think we can do this without the manual memory management done here (multiple root pointers, and manual control of m_del).

MicroPython is able to do most of this for us. We can instead use mp_obj_malloc_var_with_finaliser to allocate count_max of pbdrv_bluetooth_inquiry_result_t. The finalizer gives us a hook to safely tell the Bluetooth process to stop processing when the data is freed by the garbage collector.


In #445, I've been working on making Bluetooth tasks chronological. I think the scanner lends itself well to this pattern too:

pbio_error_t pbdrv_bluetooth_inquiry_scan_func(pbio_os_state_t *state, void *context) {

    PBIO_OS_ASYNC_BEGIN(state);

    gap_inquiry_start(duration);

    // Wait until the number of devices are found or scan timeout
    PBIO_OS_AWAIT_UNTIL(state, count == count_max || ({

        if (hci_event_is_type(event_packet, GAP_EVENT_INQUIRY_RESULT)) {
            // process a single result contained in event_packet here
            // no callbacks needed, just store it in the context provided array of results
        }

        // The wait until condition: inquiry complete.
        hci_event_is_type(event_packet, GAP_EVENT_INQUIRY_COMPLETE);
    }));

    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
}

This is also nicely extensible if we make a scan-and-connect task later.

We'll also need to make these tasks cancellable. So that we stop scanning when the program ends or the user interrupts this action. With this pattern, we can do that relatively easily.


I think we're also going to need some kind of Bluetooth loop that can queue/refuse multiple Bluetooth calls, just like we have with BLE. It wouldn't be safe to have two scans running, for example.

The current BLE loop is something like this:

until shutdown:

     send notification if something was buffered

     do broadcast task if queued

     for N peripherals:
         do peripheral task if queued

I haven't quite decided if our Bluetooth classic loop should be entirely parallel or just add to the existing loop like:

    do classic task if queued

I'm leaning towards managing it in the same loop, so we can keep ensuring that Bluetooth activity is somewhat sequential. Running them in parallel could save a little bit of time if the user was doing something with BLE and classic at the same time, but this doesn't seem worth the extra complexity.

@jaguilar
Copy link
Contributor Author

jaguilar commented Jan 9, 2026

Sounds good. If you are intending to rewrite the PR, I will just wait until that happens. Once it's done, I'll adjust the following PR to have the same format, hopefully. Not sure if it will be possible to follow exactly but we'll see.

@laurensvalk
Copy link
Member

It's mostly done, but it needs some polishing like adding cancellation.

@laurensvalk
Copy link
Member

OK, here is a draft before calling it a day. The last !deleteme commit contains an example and debugging via USB. The rationale is mostly covered above, but I'm happy to elaborate another day.

Basic testing done on Virtual Hub and EV3 (both Bluetooth chipsets). For Virtual Hub, there's a ready made vscode launch config (virtualhub0).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants